/*=========================================================================

Program:   Virtual Surgery
Language:  C++
Date:      2009-12-1
Version:   0.1
Mail:		   nelfylee@gmail.com

Copyright (c) 2009 Nelfy Lee. All rights reserved.

=========================================================================*/
#ifndef _VOLUME_PANEL_H_
#define _VOLUME_PANEL_H_

#include "VolumeSource.h"
#include "ControlPanelBase.h"
#include "ui_VolumeProperty.h"
#include "DisplayFramework.h"
#include "VolumeRenderingWidget.h"
#include "PipelineModel.h"

#include <QDebug>

#include <vtkImageIterator.h>

#include "itkImage.h"
#include "itkImageFileWriter.h"
#include "itkConnectedThresholdImageFilter.h"
#include "itkExtractImageFilter.h"


enum VolumeOperation
{
	voPlus = 1,
	voMinus,
	voXOR,
	voAND,
	voReplace
};


class VolumePanel : public ControlPanelBase
{
	Q_OBJECT

public:
	VolumePanel(QWidget *parent);
	~VolumePanel(){}

	void setDisplayFramework(DisplayFramework* display);
	void setSource(const QModelIndex& index);

private slots:
	void slotPLUS(){	volumeOperation(voPlus);}
	void slotMINUS(){	volumeOperation(voMinus);}
	void slotXOR(){	volumeOperation(voXOR);}
	void slotAND(){	volumeOperation(voAND);}
	void slotReplace(){	volumeOperation(voReplace);}

	void slotVolumeCalculation();
	void slotVolumeDivide();
	void slotColorChanged();
	void slotOpacityChanged();
	void slotExportVoxelPositions();

private:
	void volumeOperation(VolumeOperation vo);

private:
    Ui::VolumeProperty m_ui;
	Source *m_volume1;
	Source *m_volume2;
	ShadeWidget *m_colorEditor;
	ShadeWidget *m_opacityEditor;
	VolumeSourceBase *m_vsource;


private://left part is template involved

	template<class FPixelType>
	void volumeDivide()
	{
		typedef itk::Image<FPixelType, 3> FImageType;
		typedef itk::ImageRegionIterator<FImageType> ImageRegionIterator;
		typedef itk::ImageDuplicator<FImageType> ImageDuplicator;

		Source *source = currentSource();

		typename FImageType::Pointer volume = ((VolumeSource<FImageType>*)source)->getVolume();

		double *range = source->data()->GetScalarRange();

		int znum = m_ui.spin_divZ->value();
		typename FImageType::RegionType inputRegion = volume->GetLargestPossibleRegion();
		typename FImageType::SizeType  inputSize = inputRegion.GetSize();

		if ( (unsigned int)znum >= inputSize[2] )
		{
			qDebug() << "Slice number is larger than total slice number";
			return;
		}


		typename FImageType::Pointer vol1 = NULL;
		typename FImageType::Pointer vol2 = NULL;

		if ( m_ui.cb_keepDim->isChecked())
		{
			typename ImageDuplicator::Pointer dup1 = ImageDuplicator::New();
			typename ImageDuplicator::Pointer dup2 = ImageDuplicator::New();
			dup1->SetInputImage(volume);
			dup1->Update();
			dup2->SetInputImage(volume);
			dup2->Update();

			ImageRegionIterator it1(dup1->GetOutput(), dup1->GetOutput()->GetLargestPossibleRegion());
			ImageRegionIterator it2(dup2->GetOutput(), dup2->GetOutput()->GetLargestPossibleRegion());

			unsigned int i = 0;
			for ( it1.GoToBegin(), it2.GoToBegin(); !it1.IsAtEnd(); ++it1, ++it2, i++)
			{
				if ( i < znum * inputSize[0] *inputSize[1])
				{
					it2.Set(range[0]);
				}
				else
				{
					it1.Set(range[0]);
				}
			}

			vol1 = dup1->GetOutput();
			vol2 = dup2->GetOutput();
		}
		else
		{
			vol1 = FImageType::New();
			vol2 = FImageType::New();

			typename FImageType::IndexType start;
			start[0] = 0;
			start[1] = 0;
			start[2] = 0;


			typename FImageType::SizeType size1;
			size1[0] = inputSize[0];
			size1[1] = inputSize[1];
			size1[2] = znum;

			typename FImageType::SizeType size2;
			size2[0] = inputSize[0];
			size2[1] = inputSize[1];
			size2[2] = inputSize[2] - znum;

			typename FImageType::RegionType region1;
			region1.SetSize(size1);
			region1.SetIndex(start);
			
			typename FImageType::RegionType region2;
			region2.SetSize(size2);
			region2.SetIndex(start);

			vol1->SetRegions(region1);
			vol2->SetRegions(region2);

			vol1->Allocate();
			vol2->Allocate();

			ImageRegionIterator it(volume, volume->GetLargestPossibleRegion());
			ImageRegionIterator it1(vol1, vol1->GetLargestPossibleRegion());
			ImageRegionIterator it2(vol2, vol2->GetLargestPossibleRegion());

			it.GoToBegin();
			it1.GoToBegin();
			it2.GoToBegin();

			
			for ( unsigned int i = 0; !it.IsAtEnd(); ++it, i++)
			{
				if ( i <= znum * inputSize[0] *inputSize[1])
				{
					it1.Set(it.Get());
					++it1;
				}
				else
				{
					it2.Set(it.Get());
					++it2;
				}
			}

			vol1->SetSpacing(volume->GetSpacing());
			vol2->SetSpacing(volume->GetSpacing());
		}




		VolumeSource<FImageType> *part1 = 
			new VolumeSource<FImageType>(QString("Part#%1-1").arg(source->rowCount()+1));
		part1->setVolume(vol1);

		source->addChild(part1);

		VolumeSource<FImageType> *part2 = 
			new VolumeSource<FImageType>(QString("Part#%1-2").arg(source->rowCount()+1));
		part2->setVolume(vol2);

		source->addChild(part2);
	}

	

	template<class FPixelType, class FPixelType2>
	void mathOperation(Source *sub1, Source *sub2, VolumeOperation vo)
	{
		typedef itk::Image<FPixelType, 3> FImageType;
		typedef itk::Image<FPixelType2, 3> FImageType2;
		typedef itk::ImageRegionIterator<FImageType> ImageRegionIterator;
		typedef itk::ImageDuplicator<FImageType> ImageDuplicator;

		//Source *source = currentSource();
		
		VolumeSource<FImageType>* volume1 = (VolumeSource<FImageType>*)sub1;
		VolumeSource<FImageType2>* volume2 = (VolumeSource<FImageType2>*)sub2;

		typename FImageType::PixelType *pointer1 = volume1->getVolume()->GetBufferPointer();
		typename FImageType2::PixelType *pointer2 = volume2->getVolume()->GetBufferPointer();

		typename ImageDuplicator::Pointer dup = ImageDuplicator::New();
		dup->SetInputImage(volume1->getVolume());
		dup->Update();
		dup->GetOutput()->FillBuffer(0);

		ImageRegionIterator it(dup->GetOutput(), dup->GetOutput()->GetLargestPossibleRegion());

		for ( it.GoToBegin(); !it.IsAtEnd(); ++it, ++pointer1, ++pointer2)
		{
			if ( vo == voXOR || vo == voPlus)
			{
				if ( *pointer1 != 0 || *pointer2 != 0)
					it.Set(1);
			}
			else if ( vo == voAND)
			{
				if ( *pointer1 != 0 && *pointer2 != 0)
					it.Set(1);
			}
			else if ( vo == voMinus)
			{
				if ( *pointer1 != 0 && *pointer2 != 0)
				{
					it.Set(0);
				}
			}
			else if ( vo == voReplace)
			{
				if ( *pointer2 != 0)
				{
					it.Set(*pointer1);
				}
			}
		}

		QString sourceName;

		if ( vo == voXOR)
		{
			sourceName = QString("%1_xor_%2").arg(sub1->text()).arg(sub2->text());
		}
		else if ( vo == voAND)
		{
			sourceName = QString("%1_and_%2").arg(sub1->text()).arg(sub2->text());
		}
		else if ( vo == voPlus)
		{
			sourceName = QString("%1_+_%2").arg(sub1->text()).arg(sub2->text());
		}
		else if ( vo == voMinus)
		{
			sourceName = QString("%1_-_%2").arg(sub1->text()).arg(sub2->text());
		}
		else if ( vo == voReplace)
		{
			sourceName = QString("%1-Rep").arg(sub2->text());
		}


		VolumeSource<FImageType> *targetSource = new VolumeSource<FImageType>(sourceName);
		targetSource->setVolume(dup->GetOutput());

		m_pipelineModel->addSource(targetSource);
	}

	template<class FPixelType>
	void volumeCalculation()
	{
		int count = 0;
		double spacing[3];

		vtkImageData *image = currentSource()->data();
		image->GetSpacing(spacing);


		FPixelType *ptr = (FPixelType*)image->GetScalarPointer();
		for ( int i = 0; i < image->GetNumberOfPoints(); i++)
		{
			if ( *ptr++ != 0)
				count++;
		}

		QString size = QString("%1").arg(spacing[0]*spacing[1]*spacing[2]*count/1000);

		m_ui.le_volumeSize->setText(size);
	}

	template<class FPixelType>
	void exportVoxelPositions(QString fileName)
	{
		int x = 1;
		int y = 1;
		int z = 1;
		double spacing[3];
		int dim[3];

		QFile file(fileName);
		if ( !file.open(QIODevice::WriteOnly|QIODevice::Text))
			return;

		QTextStream out(&file);

		vtkImageData *image = currentSource()->data();
		image->GetSpacing(spacing);
		image->GetDimensions(dim);

		printf("Dimension:%d,%d,%d\n", dim[0], dim[1], dim[2]);

		FPixelType *ptr = (FPixelType*)image->GetScalarPointer();
		for ( int i = 0; i < image->GetNumberOfPoints(); i++)
		{
			if ( x%dim[0] == 0) 
			{
				x=1;
				y+=1;
			}

			if ( y%dim[1] == 0)
			{
				y=1;
				z+=1;
			}

			if ( *ptr++ != 0)
			{
				out << x << "\t" << y << "\t" << z << endl;
			}

			x++;
		}

		file.close();
	}
	
	template<class FPixelType>
	void cutVolume()
	{
		typedef itk::Image<FPixelType, 2> FImageType2D;
		typedef itk::Image<FPixelType, 3> FImageType3D;
		typedef itk::ImageDuplicator<FImageType3D> ImageDuplicator3D;
		typedef itk::ImageRegionIterator<FImageType3D> ImageRegionIterator3D;
		typedef itk::ConnectedThresholdImageFilter<FImageType2D, FImageType2D> CTFilterType2D;
		typedef itk::ConnectedThresholdImageFilter<FImageType3D, FImageType3D> CTFilterType3D;

        
        /*

		Source *source = currentSource();

		// get seed
		QStringList sl = m_ui.cb_seedList->currentText().split(",");
		typename FImageType2D::IndexType seed2D;
		typename FImageType3D::IndexType seed3D;

		seed3D[0] = sl.at(0).toInt();
		seed3D[1] = sl.at(1).toInt();
		seed3D[2] = sl.at(2).toInt();

		typename FImageType3D::Pointer volume = ((VolumeSource<FImageType3D> *)source)->getVolume();
		typename ImageDuplicator3D::Pointer duplicator = ImageDuplicator3D::New();
		duplicator->SetInputImage(volume);
		duplicator->Update();


		typename FImageType3D::RegionType inputRegion = volume->GetLargestPossibleRegion();
		typename FImageType3D::SizeType  inputSize = inputRegion.GetSize();


		typename FImageType3D::PixelType *targetBuf = duplicator->GetOutput()->GetBufferPointer();

		typename CTFilterType2D::Pointer filter2D = CTFilterType2D::New();
		typename CTFilterType3D::Pointer filter3D = CTFilterType3D::New();

		int nextSlice = 0;

		seed2D[0] = seed3D[0];
		seed2D[1] = seed3D[1];

		for ( int i = 1; i <= 3; i++)
		{
			if ( m_ui.btn_goRight->isChecked())
			{
				nextSlice = seed3D[2] - i;
			}
			else
			{
				nextSlice = seed3D[2] + i;
			}

			if ( nextSlice < 0 || nextSlice >= inputSize[2])
			{
				qDebug("[Warning]: slice reached top or bottom");
				break;
			}

			targetBuf = duplicator->GetOutput()->GetBufferPointer() + nextSlice*inputSize[0]*inputSize[1];
			typename FImageType2D::Pointer slice = extractSlice<FImageType3D, FImageType2D>(volume, nextSlice);
			
			filter2D->SetInput(slice);
			filter2D->SetSeed(seed2D);
			filter2D->SetLower(1);
			filter2D->SetUpper(255);
			filter2D->SetReplaceValue(0);
			filter2D->Update();
			
			qMemCopy(targetBuf, filter2D->GetOutput()->GetBufferPointer(), inputSize[0]*inputSize[1]*sizeof(FPixelType));
		}


		filter3D->SetInput(duplicator->GetOutput());
		filter3D->SetSeed(seed3D);
		filter3D->SetLower(1);
		filter3D->SetUpper(255);
		filter3D->SetReplaceValue(1);
		filter3D->Update();


		VolumeSource<FImageType3D> *targetSource = 
			new VolumeSource<FImageType3D>(QString("Part#%1").arg(source->rowCount()+1));
		targetSource->setVolume(filter3D->GetOutput());


		typename FImageType3D::PixelType *volumeBuffer = volume->GetBufferPointer();

		ImageRegionIterator3D removedPartIt(filter3D->GetOutput(), filter3D->GetOutput()->GetLargestPossibleRegion());
		
		for ( removedPartIt.GoToBegin();!removedPartIt.IsAtEnd(); ++removedPartIt, ++volumeBuffer)
		{
			if ( removedPartIt.Get())
			{
				*volumeBuffer = 0;
			}
		}

		source->addChild(targetSource);
		((VolumeSource<FImageType3D> *)source)->setVolume(volume);*/
	}


	template<class FInputImageType, class FOutputImageType>
	typename FOutputImageType::Pointer extractSlice(typename FInputImageType::Pointer volume, int num)
	{
		typedef itk::ExtractImageFilter<FInputImageType, FOutputImageType> SliceExtractor;
		typename SliceExtractor::Pointer sliceExtractor = SliceExtractor::New();


		typename FInputImageType::RegionType inputRegion = volume->GetLargestPossibleRegion();
		typename FInputImageType::SizeType  inputSize = inputRegion.GetSize();
		inputSize[2] = 0;


		typename FInputImageType::IndexType start = inputRegion.GetIndex();
		start[2] = num;

		typename FInputImageType::RegionType desiredRegion;
		desiredRegion.SetSize(inputSize);
		desiredRegion.SetIndex(start);

		sliceExtractor->SetExtractionRegion(desiredRegion);
		sliceExtractor->SetInput(volume);
		sliceExtractor->Update();

		return sliceExtractor->GetOutput();
	}
};


#endif

